package com.tang.lts.core.nio.loop;

import com.tang.lts.core.constant.Constants;
import com.tang.lts.core.nio.channel.NioChannel;
import com.tang.lts.core.nio.processor.NioProcessor;

import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;

/**
 * @author Tang
 * @classname NioSelectorLoop
 * @description [  ]
 * @date 2022/5/13 10:47
 */
public class NioSelectorLoop {

    private Selector selector;

    private SelectorWork selectorWork;

    private static Boolean isLinuxSystem;

    private volatile boolean running;

    static {
        if (Constants.OS_NAME.contains("linux")) {
            isLinuxSystem = true;
        }
    }

    public NioSelectorLoop(String name, NioProcessor nioProcessor) {
        // 创建 selector
        this.selector = openSelector();
        // 创建工作线程
        this.selectorWork = new SelectorWork(name, nioProcessor);
    }

    public void start() {
        running = true;
        // 启动工作线程,等待连接
        this.selectorWork.start();
    }

    public Selector selector() {
        return this.selector;
    }

    private Selector openSelector() {
        Selector selector = null;
        if (isLinuxSystem()) {
            try {
                Class<?> aClass = Class.forName("sun.nio.ch.EPollSelectorProvider");
                if (aClass != null) {
                    Method method = aClass.getMethod("provider");
                    if (method != null) {
                        SelectorProvider provider = (SelectorProvider) method.invoke(null);
                        selector = SelectorProvider.provider().openSelector();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (selector == null) {
            try {
                selector = SelectorProvider.provider().openSelector();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return selector;
    }

    private void select() {

    }

    public class SelectorWork extends Thread {

        private NioProcessor nioProcessor;

        public SelectorWork(String name, NioProcessor nioProcessor) {
            super(name);
            setDaemon(true);
            this.nioProcessor = nioProcessor;
        }

        @Override
        public void run() {
            while (running) {
                select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                if (selectionKeys.isEmpty()) {
                    continue;
                }
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    selectionKeys.remove(key);

                    if (!key.isValid()) {
                        continue;
                    }
                    if (key.isConnectable()) {
                        doConnect(key);
                    }
                    if (key.isAcceptable()) {
                        doAccept(key);
                    }
                    if (key.isValid() && key.isReadable()) {
                        doRead(key);
                    }
                    if (key.isValid() && key.isWritable()) {
                        doWrite(key);
                    }
                }
            }
        }

        private void doWrite(SelectionKey key) {
            NioChannel channel = (NioChannel) key.attachment();
            nioProcessor.flush(channel);
        }

        private void doRead(SelectionKey key) {
            NioChannel channel = (NioChannel) key.attachment();
            nioProcessor.read(channel);
        }

        private void doAccept(SelectionKey key) {
            nioProcessor.accept(key);
        }

        private void doConnect(SelectionKey key) {
            nioProcessor.connect(key);
        }

    }

    public Boolean isLinuxSystem() {
        return isLinuxSystem;
    }
}